<?php
// cnbbx V8.0 你值得信赖的PHP框架

namespace cnbbx;

use Exception;
use Throwable;

class Env
{

    public static $database;

    public static $pool = [];

    /**
     * 环境变量数据
     * @var array
     */
    protected static $data = [];

    /**
     * 读取环境变量定义文件
     * @access public
     * @param string $file 环境变量定义文件
     * @return void
     */
    public static function load(string $file)
    {
        is_file($file) && self::set(parse_ini_file($file, true) ?: []);
    }

    /**
     * 设置环境变量值
     * @access public
     * @param string|array $env 环境变量
     * @param mixed $value 值
     * @return void
     */
    public static function set($env, $value = null)
    {
        if (is_array($env)) {
            $env = array_change_key_case($env, CASE_UPPER);
            foreach ($env as $key => $val) {
                if (is_array($val)) {
                    foreach ($val as $k => $v) {
                        self::$data[$key . '_' . strtoupper($k)] = $v;
                    }
                } else {
                    self::$data[$key] = $val;
                }
            }
        } else {
            $name = strtoupper(str_replace('.', '_', $env));
            self::$data[$name] = $value;
        }
    }

    /**
     * 检测是否存在环境变量
     * @access public
     * @param string $name 参数名
     * @return bool
     */
    public static function has(string $name)
    {
        return !is_null(self::get($name));
    }

    /**
     * 获取环境变量值
     * @access public
     * @param string $name 环境变量名
     * @param mixed $default 默认值
     * @return mixed
     */
    public static function get(string $name = null, $default = null)
    {
        if (is_null($name)) {
            return self::$data;
        }
        $name = strtoupper(str_replace('.', '_', $name));
        if (isset(self::$data[$name])) {
            return self::$data[$name];
        }
        return self::getEnv($name, $default);
    }

    protected static function getEnv(string $name, $default = null)
    {
        $result = getenv('PHP_' . $name);
        if (false === $result) {
            return $default;
        }
        if ('false' === $result) {
            $result = false;
        } elseif ('true' === $result) {
            $result = true;
        }
        if (!isset(self::$data[$name])) {
            self::$data[$name] = $result;
        }
        return $result;
    }
}

class Session
{
    private $container = [];

    protected static $session_interface = null;
    private $request;
    private $sessionId;
    private $response;

    /**
     * Session constructor.
     * @param $sessionId
     * @param null $request
     * @param null $response
     * @throws Exception
     */
    protected function __construct($sessionId, $request = null, $response = null)
    {
        $this->sessionId = $sessionId;
        $this->request = $request;
        $this->response = $response;
        $sessionPath = BASE_PATH . 'runtime' . DS . 'session';
        if (!is_dir($sessionPath)) {
            if (!@mkdir($sessionPath, 0777, true)) {
                trigger_error("Can not create $sessionPath");
            }
        }
        file_exists($sessionPath . DS . $this->sessionId . '.sess') && $this->container = self::unserialize_php(file_get_contents($sessionPath . DS . $this->sessionId . '.sess'));
    }

    public static function getSessionInterface($request = null, $response = null)
    {
        if (!self::$session_interface) {
            $sessionName = session_name();
            $sessionId = session_id();
            if ($request && $response) {
                if (isset($request->cookie[$sessionName])) {
                    $sessionId = $request->cookie[$sessionName];
                }
                $request->sessionId = $sessionId;
                $cookie = session_get_cookie_params();
                $lifeTime = null;
                if ($cookie['lifetime']) {
                    $lifeTime = time() + $cookie['lifetime'];
                }
                $response->cookie($sessionName, $sessionId, $lifeTime, $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']);
                self::$session_interface = new static($sessionId, $request, $response);
            } else {
                self::$session_interface = new static($sessionId);
            }
        }
        return self::$session_interface;
    }

    private static function unserialize_php($session_data)
    {
        $return_data = array();
        $offset = 0;
        while ($offset < strlen($session_data)) {
            if (!strstr(substr($session_data, $offset), "|")) {
                throw new Exception("invalid data, remaining: " . substr($session_data, $offset));
            }
            $pos = strpos($session_data, "|", $offset);
            $num = $pos - $offset;
            $varname = substr($session_data, $offset, $num);
            $offset += $num + 1;
            $data = unserialize(substr($session_data, $offset));
            $return_data[$varname] = $data;
            $offset += strlen(serialize($data));
        }
        return $return_data;
    }

    public function offsetSet($offset, $value)
    {
        if (is_null($offset)) {
            $this->container[] = $value;
        } else {
            $this->container[$offset] = $value;
        }
        $fileStr = "";
        foreach ($this->container as $key => $value) {
            $fileStr .= "{$key}|" . serialize($value);
        }
        file_put_contents(BASE_PATH . 'runtime' . DS . 'session' . DS . $this->sessionId . '.sess', $fileStr);
    }

    public function offsetUnset($offset)
    {
        unset($this->container[$offset]);
    }

    public function offsetExists($offset)
    {
        return isset($this->container[$offset]);
    }

    public function offsetGet($offset)
    {
        return isset($this->container[$offset]) ? $this->container[$offset] : '';
    }
}

class Http extends Env
{
    private $app = 'app';
    private $controller = 'main';
    private $function = 'index';

    public function __construct($app = null)
    {
        $app && $this->app = $app;
    }

    /**
     * @param string $switch
     * @param int $port
     */
    public function run($switch = 'web', $port = 80)
    {
        session_start();
        $response = new Response();
        $switch == 'web' && PHP_SAPI == 'cli' && $switch = 'cli';
        switch ($switch) {
            case 'cli':
                if (isset($GLOBALS['argv'][1])) {
                    $this->init([], [], [], []);
                    config('Response', $response);
                    $arr = explode('/', $GLOBALS['argv'][1]);
                    $this->app = !empty($arr[1]) ? strtolower($arr[1]) : 'home';
                    $this->controller = !empty($arr[2]) ? strtolower($arr[3]) : 'main';
                    $this->function = !empty($arr[3]) ? strtolower($arr[4]) : 'index';
                    $controller_name = implode("\\", array_slice($arr, 0, 4));
                    if (!class_exists($controller_name, true)) {
                        echo ("Err: Controller '$controller_name' is not exists!\r\n");
                    }
                    if (!method_exists($controller_name, $this->function)) {
                        echo ("Err: Method '$this->function' of '$controller_name' is not exists!\r\n");
                    }
                    $controller_obj = new $controller_name();
                    $response->setContent(call_user_func_array(array($controller_obj, $this->function), count($arr) > 5 ? array_slice($arr, 5) : array()));
                    $response->send();
                    $this->end($response);
                }
                exit;
                break;
            case 'web':
                $sess = Session::getSessionInterface();
                $this->init(array_merge($_GET, $_POST), $_SERVER, $_COOKIE, apache_request_headers());
                try {
                    $response->setHeader(['Server' => 'Cnbbx/v1.0']);
                    $response = $this->runWithResponse($response);
                } catch (Throwable $e) {
                    if (config("APP_DEBUG")) {
                        try {
                            $template_data = View::compile('exception.dwt', true);
                            $response->setContent(eval("?>" . $template_data));
                        } catch (Exception $e) {
                            $response->setContent($e->getMessage());
                        }
                    } else {
                        $response->setContent("err:500");
                    }
                }
                $response->send();
                $this->end($response);
                break;
            case 'swoole':
                $httpServer = new \Swoole\Http\Server("0.0.0.0", $port);
                echo "WebServer in Run: http://localhost:{$port}\n\n";
                $httpServer->on('request', function ($_request, $_response) use ($response) {
                    $this->app = 'app';
                    $this->controller = 'main';
                    $this->function = 'index';
                    $response->setResponse($_response);
                    $sess = Session::getSessionInterface($_request, $_response);
                    $this->init(array_merge(empty($_request->get) ? [] : $_request->get, empty($_request->post) ? [] : $_request->post), $_request->server, $_request->cookie, $_request->header);
                    try {
                        if ($_request->server['request_uri'] == '/favicon.ico') {
                            $response->setContent(' ');
                        } else {
                            $response->setHeader(['Server' => 'Cnbbx/v1.0', 'Content-type' => 'text/html; charset=utf-8']);
                            $response = $this->runWithResponse($response);
                        }
                    } catch (Throwable $e) {
                        if (config("APP_DEBUG")) {
                            try {
                                $template_data = View::compile('exception.dwt', true);
                                ob_start();
                                eval('?>' . $template_data);
                                $template_data = ob_get_contents();
                                ob_end_clean();
                                $response->setContent($template_data);
                            } catch (Exception $e) {
                                $response->setContent($e->getMessage());
                            }
                        } else {
                            $response->setContent("err:500");
                        }
                    }
                    $response->send();
                    $this->end($response);
                });
                $httpServer->set([
                    'document_root' => BASE_PATH . '/public',
                    'enable_static_handler' => true,
                    'worker_num' => 4,
                    'package_max_length' => 5 * 1024 * 1024,
                    'buffer_output_size' => 20 * 1024 * 1024,
                ]);
                $httpServer->start();
                break;
        }
    }

    /**
     * 框架初始化
     * @param $Request
     * @param $Server
     * @param $Cookie
     * @param $Header
     */
    public function init($Request, $Server, $Cookie, $Header)
    {
        require_once __DIR__ . DS . 'common.php';
        is_file(BASE_PATH . DS . 'vendor' . DS . 'autoload.php') && require_once BASE_PATH . DS . 'vendor' . DS . 'autoload.php';
        config('Request', $Request);
        config('Server', $Server);
        config('Cookie', $Cookie);
        config('Headers', $Header);
        $this->load(BASE_PATH . DS . '.env');
        spl_autoload_register(function ($className) {
            $classPath = BASE_PATH . str_replace("\\", DS, $className) . '.php';
            is_file($classPath) && require_once $classPath;
        });
        file_exists(BASE_PATH . $this->app . DS . 'common.php') && require_once BASE_PATH . $this->app . DS . 'common.php';
        config('Http', $this);
    }

    private function runWithResponse(Response $response)
    {
        config('Response', $response);
        $server = config('Server');
        $request_uri = isset($server['REQUEST_URI']) ? $server['REQUEST_URI'] : $server['request_uri'];
        $route = explode("/", str_replace(strstr($request_uri, '?'), '', $request_uri));
        $urlMap = array_slice($route, 1, 2);
        isset($urlMap[0]) && !empty($urlMap[0]) && $this->controller = $urlMap[0];
        isset($urlMap[1]) && !empty($urlMap[1]) && $this->function = $urlMap[1];
        $class = "{$this->app}\\controller\\{$this->controller}"; // @todo 高级路由处理 dump(Route::getRules());
        if (class_exists($class)) {
            $controller = new $class;
            if (count($route) > 3) {
                foreach ($route as $key => $row) {
                    $route[$key] = urldecode($row);
                }
            }
            if (method_exists($controller, $this->function)) {
                $response->setContent(call_user_func_array(array($controller, $this->function), count($route) > 3 ? array_slice($route, 3) : array()));
            } else {
                throw new Exception("is not find {$class} function {$this->function}!");
            }
        } else {
            $response->header['HTTP/1.1 404 Not Found'] = '';
            $response->header['status'] = "404 Not Found";
            // throw new Exception("is not find {$class}!");
        }
        return $response;
    }

    /**
     * @param Response $response
     */
    public function end($response)
    {
        if (function_exists('fastcgi_finish_request')) {
            fastcgi_finish_request();
        }
        if ($response->getResponse()) {
            $response->getResponse()->end();
        }
    }

    public function getApp()
    {
        return $this->app;
    }

    public function getController()
    {
        return $this->controller;
    }

}

class Response
{
    public $header;
    private $content;
    private $response;

    public function send()
    {
        $data = $this->getContent();
        if (is_array($data)) {
            $this->header["Content-type"] = "application/json;charset=utf-8";
            PHP_SAPI != 'cli' && $data = json_encode($data, true);
        }
        if ($this->response && !empty($this->header)) {
            foreach ($this->header as $name => $val) {
                $this->response->header($name, $val);
            }
        } elseif (!headers_sent() && !empty($this->header)) {
            http_response_code(200);
            foreach ($this->header as $name => $val) {
                header($name . (!is_null($val) ? ':' . $val : ''));
            }
        }
        $this->sendData($data);
        $this->setContent(null);
    }

    public function getContent($callback = '')
    {
        ob_start();
        ob_implicit_flush(0);
        if (!empty($callback)) {
            $callback();
        }
        return $this->content;
    }

    public function setContent($content)
    {
        $this->content = $content;
    }

    public function sendData($data)
    {
        if ($this->response) {
            $this->response->write(is_array($data) ? json_encode($data) : $data);
        } else {
            print_r($data);
        }
    }

    public function getHeader()
    {
        return $this->header;
    }

    public function setHeader($header)
    {
        $this->header = $header;
    }

    public function getResponse()
    {
        return $this->response;
    }

    public function setResponse($response)
    {
        $this->response = $response;
    }
}

class Controller
{
    protected $middleware = [];
    public $Request = [];
    public $Server = [];
    public $Response = [];

    public function __construct()
    {
        $this->Request = config('Request');
        $this->Server = config('Server');
        $this->Response = Response();
        // 执行中间件
        foreach ($this->middleware as $middleware) {
            new $middleware($this);
        }
    }

    /**
     * 跳转程序
     * 应用程序的控制器类可以覆盖该函数以使用自定义的跳转程序
     * @param string $url 需要前往的地址
     * @param int $delay 延迟时间
     * @return string
     */
    public function jump($url, $delay = 0)
    {
        return "<html><head><meta http-equiv='refresh' content='{$delay};url={$url}'></head><body></body></html>";
    }

    /**
     * 错误提示程序
     * 应用程序的控制器类可以覆盖该函数以使用自定义的错误提示
     * @param string $msg 错误提示需要的相关信息
     * @param string $url 跳转地址
     * @return string
     */
    public function error($msg, $url = '')
    {
        $url = empty($url) ? "window.history.back();" : "location.href=\"{$url}\";";
        return "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><title>{$msg}</title><script>function cnbbx_tips(){alert(\"{$msg}\");{$url}}</script></head><body onload=\"cnbbx_tips()\"></body></html>";
    }

    /**
     * 成功提示程序
     * 应用程序的控制器类可以覆盖该函数以使用自定义的成功提示
     * @param string $msg 成功提示需要的相关信息
     * @param string $url 跳转地址
     * @return string
     */
    public function success($msg, $url = '')
    {
        return "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><title>{$msg}</title><script>function cnbbx_tips(){alert(\"{$msg}\");{$url}}</script></head><body onload=\"cnbbx_tips()\"></body></html>";
    }
}

class Model
{

    private static $conn;

    // 静态连接池
    protected $database = '';

    // 数据库主机ip
    protected $charset = '';

    // 数据库登录用户名
    protected $table = '';

    // 数据库登录密码
    protected $pk = 'id';

    // 数据库端口
    /** @var \PDO $db */
    private $db;

    // 默认数据库连接库名
    private $host = '';

    // 默认连接数据库编码
    private $user = '';

    // 表名
    private $password = '';

    // 查询字段
    private $port = '';
    private $mField = '*';
    private $mWhere = [];
    private $mWhereArgs = [];
    private $mSql = '';
    private $mOrder = '';
    private $mLimit = '';
    private $mGroup = '';
    private $mHaving = '';
    private $mJoin = [];
    private $mAllowField = [];
    private $mData = [];
    private $mSetAttr = [];
    private $mGetAttr = [];
    private $mAddAttr = [];
    private $mValidate = false;
    private $mHidden = [];
    private $mPdoAttribute = [];
    private $mErrorCode = 0;
    private $mErrorInfo = '';
    private $mContainer = array();

    // 模型初始化
    public function __construct($host = null, $user = null, $password = null, $port = null, $database = null, $charset = null)
    {
        $this->host = $host ?? config('DATABASE_HOSTNAME');
        $this->user = $user ?? config('DATABASE_USERNAME');
        $this->password = $password ?? config('DATABASE_PASSWORD');
        $this->port = $port ?? config('DATABASE_HOSTPORT');
        $this->database = $database ?? config('DATABASE_DATABASE');
        $this->charset = $charset ?? config('DATABASE_CHARSET');
        $this->init();
    }

    protected static function init()
    {
    }

    /**
     * 获取对象实例
     * @param string $host MySQL ip
     * @param string $user MySQL 用户名
     * @param string $password MySQL 密码
     * @param int $port MySQL 端口
     * @param string $database 数据库名
     * @param string $charset MySQL 连接字符集
     * @return Model
     */
    public static function getInstance($host, $user, $password, $port = 3306, $database = 'test', $charset = 'utf8')
    {
        $key = md5($host . $user . $password . $port);
        $conn = self::getCurrentConn($key);
        if ($conn === null) {
            $conn = new self($host, $user, $password, $port, $database, $charset);
            self::$conn[$key] = $conn;
        }
        return $conn;
    }

    private static function getCurrentConn($config)
    {
        if (!empty(self::$conn[$config])) {
            return self::$conn[$config];
        }
        return null;
    }

    public function getTable()
    {
        return $this->table;
    }

    public function setTable($table)
    {
        $this->table = $table;
        return $this;
    }

    public function getDatabase()
    {
        return $this->database;
    }

    public function setDatabase($database)
    {
        $this->database = $database;
        return $this;
    }

    /**
     * 保存where条件数据
     * @return array[]|string[]
     */
    public function saveWhereData()
    {
        return [
            'mField' => $this->mField,
            'mWhere' => $this->mWhere,
            'mWhereArgs' => $this->mWhereArgs,
            'mOrder' => $this->mOrder,
            'mLimit' => $this->mLimit,
            'mGroup' => $this->mGroup,
            'mHaving' => $this->mHaving,
            'mJoin' => $this->mJoin,
            'mAllowField' => $this->mAllowField,
            'mData' => $this->mData,
            'mGetAttr' => $this->mGetAttr,
            'mSetAttr' => $this->mSetAttr,
            'mAddAttr' => $this->mAddAttr,
            'mValidate' => $this->mValidate,
            'mHidden' => $this->mHidden,
            'mPdoAttribute' => $this->mPdoAttribute,
        ];
    }

    /**
     * 恢复where条件数据
     * @param array $data
     */
    public function recoverWhereData($data)
    {
        if (!empty($data)) {
            foreach ($data as $k => $v) {
                $this->$k = $v;
            }
        }
    }

    /**
     * 字段选择
     * @param string $field 要查询的字段，多个逗号分隔
     * @return Model
     */
    public function field($field)
    {
        $this->mField = $field;
        return $this;
    }

    /**
     * where or 条件
     * @param string $field 字段
     * @param string $value 值
     * @param string $op 操作符
     * @return Model
     */
    public function whereOr($field, $value = null, $op = '=')
    {
        return $this->whereMap($field, $value, $op, false);
    }

    /**
     * where条件
     * @param string|array $field
     * @param string $value
     * @param string $op
     * @param boolean $and
     * @return $this
     */
    public function whereMap($field, $value = null, $op = '=', $and = true)
    {
        $andStr = 'and';
        if (!$and) {
            $andStr = 'or';
        }
        if (is_array($field)) {
            foreach ($field as $k => $v) {
                if (is_array($v)) {
                    switch (count($v)) {
                        case 1:
                            $this->mWhere[] = [
                                $k,
                                $v[0],
                                $op,
                                $andStr,
                            ];
                            break;
                        case 2:
                            $this->mWhere[] = [
                                $k,
                                $v[0],
                                $v[1],
                                $andStr,
                            ];
                            break;
                    }
                } else {
                    $this->mWhere[] = [
                        $k,
                        $v,
                        $op,
                        $andStr,
                    ];
                }
            }
        } else {
            $this->mWhere[] = [
                $field,
                $value,
                $op,
                $andStr,
            ];
        }
        return $this;
    }

    /**
     * 排序
     * @param string $field 排序字段
     * @param string $order 排序规则，desc 降序，asc 升序
     * @return Model
     */
    public function order($field, $order = '')
    {
        if (empty($order)) {
            $this->mOrder = 'order by ' . $this->formatOrder($field);
        } else {
            $this->mOrder = 'order by ' . $this->formatOrder($field) . ' ' . $order;
        }
        return $this;
    }

    /**
     * 格式化字段,逗号分隔
     * @param string $field
     * @return string
     */
    private function formatOrder($field)
    {
        $fieldArr = explode(',', $field);
        foreach ($fieldArr as $k => $v) {
            $vArr = explode(' ', $v);
            $vArr[0] = $this->formatField($vArr[0]);
            $fieldArr[$k] = implode(' ', $vArr);
        }
        return implode(',', $fieldArr);
    }

    /**
     * 格式化字段
     * @param string $field
     * @return string
     */
    private function formatField($field)
    {
        if ($field === '*') {
            return '*';
        }
        if (false !== strpos($field, '.')) {
            $fieldArr = explode('.', $field);
            foreach ($fieldArr as $k => $v) {
                if ($v === '*') {
                    continue;
                }
                $fieldArr[$k] = $this->formatData($v);
            }
            return implode('.', $fieldArr);
        }
        return $this->formatData($field);
    }

    /**
     * 转义数据库名
     * @param string $database
     * @return string
     */
    private function formatData($database)
    {
        $database = trim(str_ireplace('`', '', $database));
        return '`' . $database . '`';
    }

    /**
     * 分页查询
     * @param integer $page 第几页，从1开始
     * @param integer $size 分页大小
     * @return Model
     */
    public function page($page, $size = 10)
    {
        $offset = ($page - 1) * $size;
        return $this->limit($offset, $size);
    }

    /**
     * 限制
     * @param integer $offset 偏移量，从0开始
     * @param integer $size 大小
     * @return Model
     */
    public function limit($offset, $size = 0)
    {
        if ($offset < 0) {
            $offset = 0;
        }
        if (empty($size)) {
            $this->mLimit = 'limit ' . $offset;
        } else {
            $this->mLimit = 'limit ' . $offset . ',' . $size;
        }
        return $this;
    }

    /**
     * 分组
     * @param string $field 字段
     * @return Model
     */
    public function group($field)
    {
        $this->mGroup = 'group by ' . $this->formatGroup($field);
        return $this;
    }

    /**
     * 格式化字段
     * @param string $field
     * @return string
     */
    private function formatGroup($field)
    {
        $fieldArr = explode(',', $field);
        foreach ($fieldArr as $k => $v) {
            $fieldArr[$k] = $this->formatData($v);
        }
        return implode(',', $fieldArr);
    }

    /**
     * 分组
     * @param string $condition 条件
     * @return Model
     */
    public function having($condition)
    {
        $this->mHaving = 'having ' . $condition;
        return $this;
    }

    /**
     * 右连接
     * @param string $table 连接的表名
     * @param string $condition 连接条件
     * @return mixed
     */
    public function rightJoin($table, $condition)
    {
        return $this->join($table, $condition, 'right join');
    }

    /**
     * join查询
     * @param string $table 连接的表名
     * @param string $condition 连接条件
     * @param string $type 连接类型（left join，right join,inner join）
     * @return $this
     */
    public function join($table, $condition, $type = 'left join')
    {
        $this->mJoin[] = [
            $table,
            $condition,
            $type,
        ];
        return $this;
    }

    /**
     * 内连接
     * @param string $table 连接的表名
     * @param string $condition 连接条件
     * @return mixed
     */
    public function innerJoin($table, $condition)
    {
        return $this->join($table, $condition, 'inner join');
    }

    /**
     * 去重查询
     * @param string $field 字段
     * @return Model
     */
    public function distinct($field)
    {
        $this->mField = 'distinct ' . $this->formatOrder($field);
        return $this;
    }

    /**
     * 获取sql语句
     * @return string
     */
    public function getLastSql()
    {
        return $this->mSql;
    }

    /**
     * 统计数据记录总数
     * @param string $field 字段
     * @return integer
     */
    public function count($field = '*')
    {
        $num = 0;
        $this->mOrder = '';
        $this->mLimit = '';
        $this->mField = 'count(' . $this->formatField($field) . ') num_';
        $result = $this->select();
        if (!empty($result)) {
            $num = $result[0]['num_'];
        }
        return (int) $num;
    }

    /**
     * 查询
     * @return mixed[]
     */
    public function select()
    {
        $data = $this->query($this->dealSelectSql(), $this->mWhereArgs);
        $this->initializeVariable();
        return $data;
    }

    /**
     * 原生查询
     * @param $sql
     * @param array $parameters
     * @return array
     */
    public function query($sql, $parameters = [])
    {
        $data = [];
        $result = $this->preExec($sql, $parameters);
        if ($result) {
            while (true) {
                $row = $result->fetch(\PDO::FETCH_ASSOC);
                if (!$row) {
                    break;
                }
                $this->useHidden($row);
                $data[] = $row;
            }
        }
        return $data;
    }

    /**
     * 统一执行sql语句前的执行
     * @param $sql
     * @param array $mWhereArgs
     * @return bool|\PDOStatement
     */
    private function preExec($sql, $mWhereArgs = [])
    {
        $sql = trim($sql);
        $this->connect();
        $result = $this->db->prepare($sql);
        $res = $result->execute($mWhereArgs);
        $this->mSql = $sql . ' 绑定的参数：' . json_encode($mWhereArgs);
        $this->mErrorCode = $result->errorCode();
        $this->mErrorInfo = $result->errorInfo();
        if (!$res) {
            return false;
        }
        return $result;
    }

    /**
     * 连接数据库
     */
    private function connect()
    {
        $key = "PdoLink=>{$this->host}:{$this->port}";
        $this->db = config($key);
        if ($this->db === null) {
            $dsn = $this->getDsn();
            try {
                $this->db = new \PDO($dsn, $this->user, $this->password, $this->mPdoAttribute);
                config($key, $this->db);
                Env::$database = $this->database;
            } catch (Exception $e) {
                throw new \RuntimeException('数据库连接失败 ' . $key);
            }
        }
        if (Env::$database !== $this->database) {
            Env::$database = $this->database;
            $this->db->exec('use ' . $this->formatData($this->database) . ';');
        }
    }

    /**
     * 获取数据库连接dsn
     * @return string
     */
    private function getDsn()
    {
        return 'mysql:dbname=' . $this->database . ';host=' . $this->host . ';port=' . $this->port . ';charset=' . $this->charset;
    }

    /**
     * 去除隐藏字段
     * @param $row
     */
    private function useHidden(&$row)
    {
        if (!empty($this->mHidden)) {
            foreach ($this->mHidden as $v) {
                if (isset($row[$v])) {
                    unset($row[$v]);
                }
            }
        }
    }

    /**
     * 处理sql语句
     */
    private function dealSelectSql()
    {
        $where = $this->dealWhere();
        $join = $this->dealJoin();
        if (!preg_match('/^(distinct|count|avg|max|min|sum)/i', $this->mField)) {
            $this->formatFields($this->mField);
        }
        return 'select ' . $this->mField . ' from ' . $this->formatTable($this->table) . ' ' . $join . ' ' . $where . ' ' . $this->mGroup . ' ' . $this->mHaving . ' ' . $this->mOrder . ' ' . $this->mLimit;
    }

    /**
     * 处理where条件
     */
    private function dealWhere()
    {
        $where = '';
        if (!empty($this->mWhere)) {
            foreach ($this->mWhere as $v) {
                if (empty($where)) {
                    if ($v[1] === null) {
                        $where = 'where ' . $v[0];
                    } else {
                        $where = 'where ' . $this->formatField($v[0]) . ' ' . $v[2] . ' ?';
                        $this->mWhereArgs[] = $v[1];
                    }
                } else {
                    if ($v[1] === null) {
                        $where .= ' ' . $v[3] . ' ' . $v[0];
                    } else {
                        $where .= ' ' . $v[3] . ' ' . $this->formatField($v[0]) . ' ' . $v[2] . ' ?';
                        $this->mWhereArgs[] = $v[1];
                    }
                }
            }
        }
        return $where;
    }

    /**
     * 处理join条件
     * @return string
     */
    private function dealJoin()
    {
        $join = '';
        if (!empty($this->mJoin)) {
            foreach ($this->mJoin as $v) {
                if (empty($join)) {
                    $join = $v[2] . ' ' . $v[0] . ' on ' . $v[1];
                } else {
                    $join .= ' ' . $v[2] . ' ' . $v[0] . ' on ' . $v[1];
                }
            }
        }
        return $join;
    }

    /**
     * 格式化字段数组
     * @param array|string $fields 字段数组
     */
    private function formatFields(&$fields)
    {
        if (is_array($fields)) {
            foreach ($fields as $k => $v) {
                $v = $this->formatField($v);
                $fields[$k] = $v;
            }
        } else {
            $fields = $this->formatOrder($fields);
        }
    }

    /**
     * 格式化表名
     * @param string $table
     * @return string
     */
    private function formatTable($table)
    {
        return $this->formatData($table);
    }

    /**
     * 初始化查询变量
     */
    private function initializeVariable()
    {
        $this->mField = '*';
        $this->mWhere = [];
        $this->mOrder = '';
        $this->mLimit = '';
        $this->mGroup = '';
        $this->mHaving = '';
        $this->mJoin = [];
        $this->mWhereArgs = [];
        $this->mAllowField = [];
        $this->mData = [];
        $this->mGetAttr = [];
        $this->mSetAttr = [];
        $this->mAddAttr = [];
        $this->mValidate = false;
        $this->mHidden = [];
        $this->mPdoAttribute = [];
    }

    /**
     * 查找最大值
     * @param string $field 字段
     * @return number
     */
    public function max($field)
    {
        $max = 0;
        $this->mOrder = '';
        $this->mLimit = '';
        $this->mField = 'max(' . $this->formatField($field) . ') num_';
        $result = $this->select();
        if (!empty($result)) {
            $max = $result[0]['num_'];
        }
        return $max;
    }

    /**
     * 查找最小值
     * @param string $field 字段
     * @return number
     */
    public function min($field)
    {
        $min = 0;
        $this->mOrder = '';
        $this->mLimit = '';
        $this->mField = 'min(' . $this->formatField($field) . ') num_';
        $result = $this->select();
        if (!empty($result)) {
            $min = $result[0]['num_'];
        }
        return $min;
    }

    /**
     * 查找平均值
     * @param string $field 字段
     * @return number
     */
    public function avg($field)
    {
        $avg = 0;
        $this->mOrder = '';
        $this->mLimit = '';
        $this->mField = 'avg(' . $this->formatField($field) . ') num_';
        $result = $this->select();
        if (!empty($result)) {
            $avg = $result[0]['num_'];
        }
        return $avg;
    }

    /**
     * 查找总和
     * @param string $field 字段
     * @return number
     */
    public function sum($field)
    {
        $sum = 0;
        $this->mOrder = '';
        $this->mLimit = '';
        $this->mField = 'sum(' . $this->formatField($field) . ') num_';
        $result = $this->select();
        if (!empty($result)) {
            $sum = $result[0]['num_'];
        }
        return $sum;
    }

    /**
     * 查找一条数据
     * @param $id
     * @param $obj
     * @return Model|mixed
     */
    public function find($id = false, $obj = false)
    {
        $id !== false && $this->where($this->getPkName(), $id);
        $result = $this->limit('1')->select();
        if ($result) {
            extract($result[0], EXTR_SKIP);
            foreach ($result[0] as $key => $value) {
                $this->offsetSet($key, $value);
            }
            return $obj ? $this : $result[0];
        }
        return [];
    }

    /**
     * where and条件
     * @param string|array $field 字段
     * @param string $value 值
     * @param string $op 操作符
     * @return Model
     */
    public function where($field, $value = null, $op = '=')
    {
        return $this->whereMap($field, $value, $op, true);
    }

    /**
     * 查找表中的主键字段
     * @return mixed
     */
    private function getPkName()
    {
        $data = $this->query('desc ' . $this->table);
        foreach ($data as $v) {
            if ($v['Key'] === 'PRI') {
                return $v['Field'];
            }
        }
        return false;
    }

    /**
     * Assigns a value to the specified offset
     * @param mixed $offset
     * @param mixed $value
     */
    public function offsetSet($offset, $value)
    {
        if (is_null($offset)) {
            $this->mContainer[] = $value;
        } else {
            $this->mContainer[$offset] = $value;
        }
    }

    /**
     * 空查询
     * @param string $field 字段
     * @return Model
     */
    public function null($field)
    {
        $this->where($this->formatField($field) . ' is null');
        return $this;
    }

    /**
     * 非空查询
     * @param string $field 字段
     * @return Model
     */
    public function notNull($field)
    {
        $this->where($this->formatField($field) . ' is not null');
        return $this;
    }

    /**
     * 不相等查询
     * @param string $field 字段
     * @param string $value 值
     * @return Model
     */
    public function neq($field, $value)
    {
        $this->where($field, $value, '!=');
        return $this;
    }

    /**
     * 大于查询
     * @param string $field 字段
     * @param string $value 值
     * @return Model
     */
    public function gt($field, $value)
    {
        $this->where($field, $value, '>');
        return $this;
    }

    /**
     * 大于或等于查询
     * @param string $field 字段
     * @param string $value 值
     * @return Model
     */
    public function egt($field, $value)
    {
        $this->where($field, $value, '>=');
        return $this;
    }

    /**
     * 小于查询
     * @param string $field 字段
     * @param string $value 值
     * @return Model
     */
    public function lt($field, $value)
    {
        $this->where($field, $value, '<');
        return $this;
    }

    /**
     * 小于或等于查询
     * @param string $field 字段
     * @param string $value 值
     * @return Model
     */
    public function elt($field, $value)
    {
        $this->where($field, $value, '<=');
        return $this;
    }

    /**
     * 相似查询
     * @param string $field 字段
     * @param string $value 值
     * @return Model
     */
    public function like($field, $value)
    {
        $this->where($field, $value, 'like');
        return $this;
    }

    /**
     * 不相似查询
     * @param string $field 字段
     * @param string $value 值
     * @return Model
     */
    public function notLike($field, $value)
    {
        $this->where($field, $value, 'not like');
        return $this;
    }

    /**
     * 区间查询
     * @param string $field 字段
     * @param string $startValue 开始值
     * @param string $endValue 结束值
     * @return Model
     */
    public function between($field, $startValue, $endValue)
    {
        $this->where($this->formatField($field) . ' between "' . $startValue . '" and "' . $endValue . '"');
        return $this;
    }

    /**
     * 不在区间查询
     * @param string $field 字段
     * @param string $startValue 开始值
     * @param string $endValue 结束值
     * @return Model
     */
    public function notBetween($field, $startValue, $endValue)
    {
        $this->where($this->formatField($field) . ' not between "' . $startValue . '" and "' . $endValue . '"');
        return $this;
    }

    /**
     * not in查询
     * @param string $field 字段
     * @param string|array $value 值，逗号分割，或数组
     * @return Model
     */
    public function notIn($field, $value)
    {
        if (is_array($value)) {
            $value = implode(',', $value);
        }
        $this->where($this->formatField($field) . ' not in (' . $value . ')');
        return $this;
    }

    /**
     * find_in_set查询
     * @param string $field 字段
     * @param string $value 值
     * @return Model
     */
    public function findInSet($field, $value)
    {
        $this->where('find_in_set("' . $value . '",' . $this->formatField($field) . ')');
        return $this;
    }

    /**
     * 过滤字段
     * @param string|array $field 过滤字段
     * @return Model
     */
    public function allowField($field)
    {
        if (is_array($field)) {
            $this->mAllowField = $field;
        } else if ($field === true) {
            // 仅允许数据库中已有的字段填充（不会去掉主键id）
            $data = $this->query('desc ' . $this->table);
            foreach ($data as $v) {
                $this->mAllowField[] = $v['Field'];
            }
        } else {
            $this->mAllowField = explode(',', $field);
        }
        return $this;
    }

    /**
     * 添加数据
     * @param array $data
     * @return number
     */
    public function insert($data)
    {
        return $this->add($data);
    }

    /**
     * 添加数据
     * @param array $data
     * @param bool $isReplace 是否替换
     * @return number
     */
    public function add($data, $isReplace = false)
    {
        if (!empty($this->mAllowField)) {
            foreach ($data as $k => $v) {
                if (!in_array($k, $this->mAllowField, true)) {
                    unset($data[$k]);
                }
            }
        }
        $this->useHidden($data);
        if (empty($data)) {
            return 0;
        }
        $keyArr = array_keys($data);
        $this->formatFields($keyArr);
        $valueArr = array_values($data);
        $pArr = array_fill(0, count($valueArr), '?');
        $sql = 'insert';
        if ($isReplace) {
            $sql = 'replace';
        }
        $sql .= ' into ' . $this->formatTable($this->table) . ' (' . implode(',', $keyArr) . ') values (' . implode(',', $pArr) . ')';
        $num = $this->execute($sql, $valueArr);
        $this->initializeVariable();
        return $num;
    }

    /**
     * @param string $sql 原生sql语句
     * @param array $mWhereArgs 绑定的参数
     * @return number
     */
    public function execute($sql, $mWhereArgs = [])
    {
        $isInsert = false;
        if (0 === stripos($sql, 'insert')) {
            $isInsert = true;
        }
        $result = $this->preExec($sql, $mWhereArgs);
        if (empty($result)) {
            return 0;
        }
        if ($isInsert) {
            $num = $this->db->lastInsertId();
            if (empty($num) && !empty($result->rowCount())) {
                $num = $result->rowCount();
            }
        } else {
            $num = $result->rowCount();
        }
        return (int) $num;
    }

    /**
     * 添加或更新数据
     * @param array $data
     * @return number
     */
    public function replace($data)
    {
        return $this->add($data, true);
    }

    /**
     * 删除数据
     * @param integer|bool|array $id 主键id
     * @return number
     */
    public function delete($id = false)
    {
        if ($id !== false) {
            $pkName = $this->getPkName();
            if (is_array($id) || preg_match('/,/', $id)) {
                $this->in($pkName, $id);
            } else {
                $this->eq($pkName, $id);
            }
        }
        $where = $this->dealWhere();
        if (empty($where)) {
            return 0;
        }
        $sql = 'delete from ' . $this->formatTable($this->table) . ' ' . $where;
        $num = $this->execute($sql, $this->mWhereArgs);
        $this->initializeVariable();
        return $num;
    }

    /**
     * in查询
     * @param string $field 字段
     * @param string|array $value 值，逗号分割，或数组
     * @return Model
     */
    public function in($field, $value)
    {
        if (is_array($value)) {
            $value = implode(',', $value);
        }
        $this->where($this->formatField($field) . ' in (' . $value . ')');
        return $this;
    }

    /**
     * 相等查询
     * @param string $field 字段
     * @param string $value 值
     * @return Model
     */
    public function eq($field, $value)
    {
        $this->where($field, $value);
        return $this;
    }

    /**
     * 开启事务
     */
    public function startTrans()
    {
        $this->connect();
        if (!$this->db->inTransaction()) {
            $this->db->beginTransaction();
        }
    }

    /**
     * 提交事务
     */
    public function commit()
    {
        $this->connect();
        if ($this->db->inTransaction()) {
            $this->db->commit();
        }
    }

    /**
     * 回滚事务
     */
    public function rollback()
    {
        $this->connect();
        if ($this->db->inTransaction()) {
            $this->db->rollBack();
        }
    }

    /**
     * 更新字段值
     * @param string $field 字段
     * @param string $value 值
     * @return number
     */
    public function setField($field, $value)
    {
        return $this->update([$field => $value]);
    }

    /**
     * 更新语句
     * @param array $data
     * @param boolean $isBindArgs 是否使用绑定参数
     * @return number
     */
    public function update($data, $isBindArgs = true)
    {
        if (!empty($this->mAllowField)) {
            foreach ($data as $k => $v) {
                if (!in_array($k, $this->mAllowField, true)) {
                    unset($data[$k]);
                }
            }
        }
        $this->useHidden($data);
        if (empty($data)) {
            return 0;
        }
        $keyArr = array_keys($data);
        $valueArr = $isBindArgs ? array_values($data) : [];
        $keyStr = '';
        foreach ($keyArr as $k => $v) {
            $tmp = '?';
            if (!$isBindArgs) {
                $tmp = $data[$v];
            }
            if (empty($keyStr)) {
                $keyStr = $this->formatField($v) . ' = ' . $tmp;
            } else {
                $keyStr .= ',' . $this->formatField($v) . ' = ' . $tmp;
            }
        }
        $where = $this->dealWhere();
        $sql = 'update ' . $this->formatTable($this->table) . ' set ' . $keyStr . ' ' . $where;
        $num = $this->execute($sql, array_merge($valueArr, $this->mWhereArgs));
        $this->initializeVariable();
        return $num;
    }

    /**
     * 自增
     * @param string $field 自增字段
     * @param integer $num 增值
     * @return number
     */
    public function setInc($field, $num = 1)
    {
        return $this->update([$field => $this->formatField($field) . '+' . $num], false);
    }

    /**
     * 自减
     * @param string $field 自减字段
     * @param integer $num 减值
     * @return number
     */
    public function setDec($field, $num = 1)
    {
        return $this->update([$field => $this->formatField($field) . '-' . $num], false);
    }

    /**
     * 设置数据
     * @param array $data
     * @return $this
     */
    public function data($data)
    {
        $this->mData = $data;
        return $this;
    }

    /**
     * 添加数据
     * @return boolean|number
     */
    public function save()
    {
        if (empty($this->mData)) {
            return false;
        }
        return $this->add($this->mData);
    }

    /**
     * 添加多条数据
     * @param array $datas 多条数据
     * @return number[]
     */
    public function saveAll($datas)
    {
        $result = [];
        foreach ($datas as $data) {
            $result[] = $this->add($data);
        }
        return $result;
    }

    /**
     * 获取器设置
     * @param array|string $fields 需要获取的字段数组
     * @return $this
     */
    public function getAttr($fields)
    {
        if (!is_array($fields)) {
            $fields = explode(',', $fields);
        }
        $this->mGetAttr = $fields;
        return $this;
    }

    /**
     * 修改器设置
     * @param array|string $fields 需要修改的字段数组
     * @return $this
     */
    public function setAttr($fields)
    {
        if (!is_array($fields)) {
            $fields = explode(',', $fields);
        }
        $this->mSetAttr = $fields;
        return $this;
    }

    /**
     * 添加器设置
     * @param array|string $fields 需要修改的字段数组
     * @return $this
     */
    public function addAttr($fields)
    {
        if (!is_array($fields)) {
            $fields = explode(',', $fields);
        }
        $this->mAddAttr = $fields;
        return $this;
    }

    /**
     * 关联查找
     * @param string $tableName 关联表名,其它库名，请使用 . 连接
     * @param string $foreignKey 关联表外键
     * @param string $primaryKey 本表主键
     * @param string $field 关联表需要查询的字段
     * @return $this
     */
    public function with($tableName, $foreignKey, $primaryKey, $field = '')
    {
        if (!empty($field)) {
            if ($this->mField === '*') {
                $this->mField = $this->table . '.' . '*';
            }
            $this->mField .= ',' . $field;
        }
        return $this->leftJoin($tableName, $this->table . '.' . $primaryKey . '=' . $tableName . '.' . $foreignKey);
    }

    /**
     * 左连接
     * @param string $table 连接的表名
     * @param string $condition 连接条件
     * @return $this
     */
    public function leftJoin($table, $condition)
    {
        return $this->join($table, $condition);
    }

    /**
     * 切换数据库
     * @param string $database 数据库名
     * @return $this
     */
    public function changeDatabase($database)
    {
        if (Env::$database !== $database) {
            $this->execute('use ' . $this->formatData($database) . ';');
            Env::$database = $database;
            $this->database = $database;
        }
        return $this;
    }

    /**
     * 清空表数据
     * @param string $table
     */
    public function truncate($table = '')
    {
        $table = $table ?: $this->table;
        if (!empty($table)) {
            $this->execute('TRUNCATE ' . $this->formatTable($table) . ';');
        }
    }

    /**
     * 隐藏字段
     * @param string|array $field
     * @return $this
     */
    public function hidden($field)
    {
        if (!is_array($field)) {
            $field = explode(',', $field);
        }
        $this->mHidden = array_merge($this->mHidden, $field);
        return $this;
    }

    /**
     * 设置列名大小写规则
     * @param int $case 0 不做任何修改，1 大写，2 小写
     * @return $this
     */
    public function setFieldCase($case)
    {
        $this->mPdoAttribute[\PDO::ATTR_CASE] = $case;
        return $this;
    }

    /**
     * 设置错误方式
     * @param int $error 0 仅设置错误代码,1 引发 E_WARNING 错误,2 抛出 exceptions 异常
     * @return $this
     */
    public function setError($error)
    {
        $this->mPdoAttribute[\PDO::ATTR_ERRMODE] = $error;
        return $this;
    }

    /**
     * 设置空字段处理
     * @param int $emptyNull 0 不转换, 1 将空字符串转换成 NULL, 2 将 NULL 转换成空字符串
     * @return $this
     */
    public function setEmptyNull($emptyNull)
    {
        $this->mPdoAttribute[\PDO::ATTR_ORACLE_NULLS] = $emptyNull;
        return $this;
    }

    /**
     * 提取的时候将数值转换为字符串
     * @param bool $to true 转换，false 不转换
     * @return $this
     */
    public function setIntFieldToStr($to)
    {
        $this->mPdoAttribute[\PDO::ATTR_STRINGIFY_FETCHES] = $to;
        return $this;
    }

    /**
     * 指定超时的秒数。并非所有驱动都支持此选项，这意味着驱动和驱动之间可能会有差异
     * @param int $timeOut 单位：秒
     * @return $this
     */
    public function setTimeout($timeOut)
    {
        $this->mPdoAttribute[\PDO::ATTR_TIMEOUT] = $timeOut;
        return $this;
    }

    /**
     * 是否自动提交每个单独的语句
     * @param bool $autoCommit true 是，false 否
     * @return $this
     */
    public function setAutoCommit($autoCommit)
    {
        $this->mPdoAttribute[\PDO::ATTR_AUTOCOMMIT] = $autoCommit;
        return $this;
    }

    /**
     * 启用或禁用预处理语句的模拟
     * 有些驱动不支持或有限度地支持本地预处理。使用此设置强制PDO总是模拟预处理语句（如果为 TRUE ），或试着使用本地预处理语句（如果为 FALSE）。如果驱动不能成功预处理当前查询，它将总是回到模拟预处理语句上。
     * @param bool $emulate true 模拟，false 使用本地预处理语句
     * @return $this
     */
    public function setEmulateSql($emulate)
    {
        $this->mPdoAttribute[\PDO::ATTR_EMULATE_PREPARES] = $emulate;
        return $this;
    }

    /**
     * （在MySQL中可用）： 使用缓冲查询
     * @param bool $bufferQuery true 使用，false 不使用
     * @return $this
     */
    public function setBufferQuery($bufferQuery)
    {
        $this->mPdoAttribute[\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY] = $bufferQuery;
        return $this;
    }

    /**
     * 设置默认的提取模式
     * @param int $mode
     * 2 返回一个索引为结果集列名的数组
     * 4 返回一个索引为结果集列名和以0开始的列号的数组
     * 6 返回 TRUE ，并分配结果集中的列值给 PDOStatement::bindColumn() 方法绑定的 PHP 变量
     * 8 返回一个请求类的新实例，映射结果集中的列名到类中对应的属性名。如果 fetch_style 包含 \PDO::FETCH_CLASSTYPE（例如：PDO::FETCH_CLASS | \PDO::FETCH_CLASSTYPE），则类名由第一列的值决定
     * 9 更新一个被请求类已存在的实例，映射结果集中的列到类中命名的属性
     * 1 结合使用 \PDO::FETCH_BOTH 和 \PDO::FETCH_OBJ，创建供用来访问的对象变量名
     * 3 返回一个索引为以0开始的结果集列号的数组
     * 5 返回一个属性名对应结果集列名的匿名对象
     * @return $this
     */
    public function setFetchMode($mode)
    {
        $this->mPdoAttribute[\PDO::ATTR_DEFAULT_FETCH_MODE] = $mode;
        return $this;
    }

    /**
     * 获取错误码
     * @return int
     */
    public function getErrorCode()
    {
        return $this->mErrorCode;
    }

    /**
     * 获取错误信息
     * @return string
     */
    public function getErrorInfo()
    {
        $info = $this->mErrorInfo;
        if (empty($info)) {
            return '';
        }
        return '错误码：' . $info[0] . '，驱动错误码：' . $info[1] . '，错误信息：' . $info[2];
    }

    /**
     * 刷新权限
     * @return number
     */
    public function flushPrivileges()
    {
        return $this->execute('FLUSH PRIVILEGES');
    }

    /**
     * Get a data by key
     * @param $key
     * @return mixed
     */
    public function &__get($key)
    {
        return $this->mContainer[$key];
    }

    /**
     * Assigns a value to the specified data
     * @param $key
     * @param $value
     */
    public function __set($key, $value)
    {
        $this->mContainer[$key] = $value;
    }

    /**
     * Whether or not an data exists by key
     * @param $key
     * @return bool
     */
    public function __isset($key)
    {
        return isset($this->mContainer[$key]);
    }

    /**
     * UnSets an data by key
     * @param $key
     */
    public function __unset($key)
    {
        unset($this->mContainer[$key]);
    }

    /**
     * UnSets an offset
     * @param mixed $offset
     */
    public function offsetUnset($offset)
    {
        if ($this->offsetExists($offset)) {
            unset($this->mContainer[$offset]);
        }
    }

    /**
     * Whether or not an offset exists
     * @param mixed $offset
     * @return bool
     */
    public function offsetExists($offset)
    {
        return isset($this->mContainer[$offset]);
    }

    /**
     * Returns the value at specified offset
     * @param mixed $offset
     * @return mixed|null
     */
    public function offsetGet($offset)
    {
        return isset($this->mContainer[$offset]) ? $this->mContainer[$offset] : null;
    }

    private function __clone()
    {
    }
}

class View
{
    private static $data = [];
    private static $left_delimiter = '<{';
    private static $right_delimiter = '}>';

    /**
     * 模板变量赋值
     * @access public
     * @param string|array $name 模板变量
     * @param mixed $value 变量值
     */
    public static function assign($name, $value = null)
    {
        if (is_array($name)) {
            self::$data = array_merge(self::$data, $name);
        } else {
            self::$data[$name] = $value;
        }
    }

    /**
     * 解析和获取模板内容 用于输出
     * @access public
     * @param string $template 模板文件名或者内容
     * @param array $vars 模板变量
     * @return string
     */
    public static function fetch(string $template = '', array $vars = [])
    {
        $response = Response();
        return $response->getContent(function () use ($vars, $response, $template) {
            $template_data = self::compile($template . ".html");
            $response->setContent(self::exec($template_data, $vars));
        });
    }

    /**
     * 加载模板文件
     * @param $template
     * @param bool $isSys
     * @return bool|null|string|string[]
     * @throws Exception
     */
    public static function compile($template, $isSys = false)
    {
        $http = Http();
        if ($isSys) {
            $filePath = __DIR__ . DS . "dwt" . DS . $template;
        } else {
            $filePath = BASE_PATH . $http->getApp() . DS . 'view' . DS . $http->getController() . DS . $template;
        }
        if (file_exists($filePath)) {
            $template_data = file_get_contents($filePath);
            $template_data = self::_compile_structure($template_data);
            $template_data = self::_compile_function($template_data);
        } else {
            throw new Exception($filePath . " is not find!");
        }
        return $template_data;
    }

    private static function _compile_structure($template_data)
    {
        $foreach_inner_before = '<?php if(!empty($1)){ $_foreach_$3_counter = 0; $_foreach_$3_total = count($1);?>';
        $foreach_inner_after = '<?php $_foreach_$3_index = $_foreach_$3_counter;$_foreach_$3_iteration = $_foreach_$3_counter + 1;$_foreach_$3_first = ($_foreach_$3_counter == 0);$_foreach_$3_last = ($_foreach_$3_counter == $_foreach_$3_total - 1);$_foreach_$3_counter++;?>';
        $pattern_map = array(
            '<{\*([\s\S]+?)\*}>' => '<?php /* $1*/?>',
            '<{#(.*?)}>' => '<?php echo $1; ?>',
            '(<{((?!}>).)*?)(\$[\w\"\'\[\]]+?)\.(\w+)(.*?}>)' => '$1$3[\'$4\']$5',
            '(<{.*?)(\$(\w+)@(index|iteration|first|last|total))+(.*?}>)' => '$1$_foreach_$3_$4$5',
            '<{(\$[\$\w\.\"\'\[\]]+?)\snofilter\s*}>' => '<?php echo $1; ?>',
            '<{(\$[\$\w\"\'\[\]]+?)\s*=(.*?)\s*}>' => '<?php $1 =$2; ?>',
            '<{(\$[\$\w\.\"\'\[\]]+?)\s*}>' => '<?php echo htmlspecialchars($1, ENT_QUOTES, "UTF-8"); ?>',
            '<{if\s*(.+?)}>' => '<?php if ($1) : ?>',
            '<{else\s*if\s*(.+?)}>' => '<?php elseif ($1) : ?>',
            '<{else}>' => '<?php else : ?>',
            '<{break}>' => '<?php break; ?>',
            '<{continue}>' => '<?php continue; ?>',
            '<{\/if}>' => '<?php endif; ?>',
            '<{foreach\s*(\$[\$\w\.\"\'\[\]]+?)\s*as(\s*)\$([\w\"\'\[\]]+?)}>' => $foreach_inner_before . '<?php foreach( $1 as $$3 ) : ?>' . $foreach_inner_after,
            '<{foreach\s*(\$[\$\w\.\"\'\[\]]+?)\s*as\s*(\$[\w\"\'\[\]]+?)\s*=>\s*\$([\w\"\'\[\]]+?)}>' => $foreach_inner_before . '<?php foreach( $1 as $2 => $$3 ) : ?>' . $foreach_inner_after,
            '<{\/foreach}>' => '<?php endforeach; }?>',
            '<{include\s*file=(.+?)}>' => '<?php include self::compile($1); ?>',
            '<!--#include\s*virtual\s*=(.+?)-->' => '<?php echo self::compile($1); ?>',
        );
        foreach ($pattern_map as $p => $r) {
            $pattern = '/' . str_replace(array("<{", "}>"), array(self::$left_delimiter . '\s*', '\s*' . self::$right_delimiter), $p) . '/i';
            $count = 1;
            while ($count != 0) {
                $template_data = preg_replace($pattern, $r, $template_data, 1, $count);
            }
        }
        return $template_data;
    }

    private static function _compile_function($template_data)
    {
        $pattern = '/' . self::$left_delimiter . '(\w+)\s*(.*?)' . self::$right_delimiter . '/';
        return preg_replace_callback($pattern, function ($matches) {
            if (empty($matches[2])) {
                return '<?php echo ' . $matches[1] . '();?>';
            }
            $sysFunc = preg_replace('/\((.*)\)\s*$/', '<?php echo ' . $matches[1] . '($1);?>', $matches[2], -1, $count);
            if ($count) {
                return $sysFunc;
            }
            $pattern_inner = '/\b([-\w]+?)\s*=\s*(\$[\w"\'\]\[\-_>\$]+|"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"|\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'|([->\w]+))\s*?/';
            if (preg_match_all($pattern_inner, $matches[2], $matches_inner, PREG_SET_ORDER)) {
                $params = "array(";
                foreach ($matches_inner as $m) {
                    $params .= '\'' . $m[1] . "'=>" . $m[2] . ", ";
                }
                $params .= ")";
            } else {
                throw new Exception('Err: Parameters of ' . $matches[1] . ' is incorrect!');
            }
            return '<?php echo ' . $matches[1] . '(' . $params . ');?>';
        }, $template_data);
    }

    /**
     * 解析PHP代码
     * @param $php_code
     * @param $vars
     * @return string
     */
    public static function exec($php_code, $vars)
    {
        extract(array_merge(self::$data, $vars), EXTR_SKIP);
        ob_start();
        eval('?>' . $php_code);
        $template_data = ob_get_contents();
        ob_end_clean();
        return $template_data;
    }

    /**
     * 渲染内容输出
     * @access public
     * @param string $content 内容
     * @param array $vars 模板变量
     * @return string
     */
    public static function display(string $content, array $vars = [])
    {
        $response = Response();
        return $response->getContent(function () use ($vars, $response, $content) {
            $template_data = self::_compile_structure($content);
            $template_data = self::_compile_function($template_data);
            $response->setContent(self::exec($template_data, $vars));
        });
    }
}

class Route
{
    private static $rules = [];

    public static function get(string $rule, $route = null)
    {
        return self::rule($rule, $route, 'GET');
    }

    /**
     * 注册路由规则
     * @access public
     * @param string $rule 路由规则
     * @param mixed $route 路由地址
     * @param string $method 请求类型
     * @return RuleItem
     */
    public static function rule(string $rule, $route = null, string $method = '*')
    {
        if (is_string($route)) {
            $name = $route;
        } else {
            $name = null;
        }
        $method = strtolower($method);
        if ('' === $rule || '/' === $rule) {
            $rule .= '$';
        }
        $ruleItem = new RuleItem($name, $rule, $route, $method);
        self::addRuleItem($ruleItem, $method);
        return $ruleItem;
    }

    /**
     * 注册分组下的路由规则
     * @access public
     * @param  RuleItem $rule 路由规则
     * @param  string $method 请求类型
     */
    public static function addRuleItem(RuleItem $rule, string $method = '*')
    {
        if (strpos($method, '|')) {
            $method = '*';
        }
        self::$rules[$method][] = $rule;
    }

    public static function post(string $rule, $route = null)
    {
        return self::rule($rule, $route, 'POST');
    }

    public static function getRules()
    {
        return self::$rules;
    }
}

class RuleItem
{
    private $name;
    private $rule;
    private $route;
    private $method;
    private $pattern;
    private $middleware;

    public function __construct($name, $rule, $route, $method)
    {
        $this->setName($name);
        $this->setRule($rule);
        $this->setRoute($route);
        $this->setMethod($method);
    }

    public function middleware(array $array)
    {
        $this->setMiddleware($array);
    }

    public function pattern(array $array)
    {
        $this->setMethod($array);
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;
    }

    public function getRule()
    {
        return $this->rule;
    }

    public function setRule($rule)
    {
        $this->rule = $rule;
    }

    public function getRoute()
    {
        return $this->route;
    }

    public function setRoute($route)
    {
        $this->route = $route;
    }

    public function getMethod()
    {
        return $this->method;
    }

    public function setMethod($method)
    {
        $this->method = $method;
    }

    public function getPattern()
    {
        return $this->pattern;
    }

    public function setPattern($pattern)
    {
        $this->pattern = $pattern;
    }

    public function getMiddleware()
    {
        return $this->middleware;
    }

    public function setMiddleware($middleware)
    {
        $this->middleware = $middleware;
    }

}
